﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Diagnostics;

namespace Red.Toolkit.Profiler.Logic.Interpreters
{
    class JobTraceInterpreter : ProfilerDataView
    {
        private class JobChain
        {
            public ulong _ID;
            public SortedDictionary<ushort, JobEvent> _jobs;
            public List<KeyValuePair<ushort, ushort>> _deps;

            public JobChain( ulong id )
            {
                _ID = id;
                _jobs = new SortedDictionary<ushort, JobEvent>();
                _deps = new List<KeyValuePair<ushort, ushort>>();
            }
        }

        private class JobEvent : Event
        {
            public JobEvent( JobTraceInterpreter parentView, RawEvent evt, Color color )
                : base( parentView, evt, color )
            {
            }

            public override float GetWeight()
            {
                return 1.0f;
            }

            public override string GetCaption()
            {
                return "Job #" + _ID + " (Chain #" + _Chain._ID + ") - " + _CustomString;
            }

            public void SetID( ushort id )
            {
                _ID = id;
            }

            public void SetChain( JobChain chain )
            {
                _Chain = chain;
            }

            public void SetCustomString( string txt )
            {
                _CustomString = txt;
            }

            public string CustomString { get { return _CustomString; } }

            private ushort _ID;
            private JobChain _Chain;
            private string _CustomString;
        }

        private SortedDictionary<ulong, JobChain> _jobChains;

        JobChain GetJobChain( ulong id )
        {
            if ( _jobChains.ContainsKey( id ) )
                return _jobChains[id];
            else
            {
                JobChain chain = new JobChain( id );
                _jobChains.Add( id, chain );
                return chain;
            }
        }

        public JobTraceInterpreter( ProfilerData data, ProfilerTraceInstance trace )
            : base( "Job Trace", data.FilePath, data, trace )
        {
            Style.SetColor( (int)DisplayType.JobChain, Color.FromArgb( 255, 100, 255, 255 ) );
            Style.SetColor( (int)DisplayType.Job, Color.FromArgb( 255, 255, 100, 100 ) );

            _jobChains = new SortedDictionary<ulong, JobChain>();
        }

        // display types
        private enum DisplayType
        {
            Selection = 0,
            JobChain = 1,
            Job = 2,
            Invalid = -1
        };

        // general signals (must match C++)
        private enum SignalType
        {
            JobAdded = 1,
            JobStalled = 2,
            JobResumed = 3,
            JobDependency = 4,
        };

        // general block types (must match c++)
        private enum BlockType
        {
            JobChain = 1,
            Job = 2,
        };

        protected override bool AnalyzeRawData()
        {
            // analyze threads
            foreach ( var t in profilerData.Threads )
            {
                // empty or noise thread
                if ( t.Events.Count == 0 )
                    continue;

                // create group per thread
                string threadName = t.Name;
                Group threadGroup = AddGroup( threadName, true );

                // add events
                foreach ( var e in t.Events )
                {
                    var evt = e.Value;

                    // only blocks
                    if ( evt.Def.Type == ProfilerDefinitionType.Block )
                    {
                        // get emitted block type
                        BlockType blockType = (BlockType)evt.Def.ID;

                        if ( blockType == BlockType.Job )
                        {
                            if ( evt.Type == RawEventType.BlockStart )
                            {
                                JobEvent jobEvent = new JobEvent( this, evt, Style.BlockColors[ (int)BlockType.Job ] );

                                uint chainIdLow = evt.Param( 1 );
                                uint chainIdHigh = evt.Param( 2 );
                                ulong chainId = ((ulong)chainIdHigh << 32) | (ulong)chainIdLow;

                                ushort id = (ushort)evt.Param( 3 );
                                jobEvent.SetID( id );

                                JobChain chain = GetJobChain( chainId );
                                jobEvent.SetChain( chain );
                                chain._jobs.Add( id, jobEvent );

                                jobEvent.SetCustomString( profilerData.GetString( evt.Param( 0 ) ) );

                                Debug.Write( "Job Start (job=" + id + ", chain=" + chainId + ") " + evt.StartTime + ", thread=" + t.Name + "\n" );
                                AddEvent( jobEvent, threadGroup );
                            }

                            continue;
                        }
                    }
                    else if ( evt.Def.Type == ProfilerDefinitionType.Signal && evt.Type == RawEventType.Signal )
                    {
                        // get emitted signal type
                        SignalType signalType = (SignalType)evt.Def.ID;

                        if ( signalType == SignalType.JobDependency )
                        {
                            uint chainIdLow = evt.Param( 0 );
                            uint chainIdHigh = evt.Param( 1 );
                            ulong chainId = ((ulong)chainIdHigh << 32) | chainIdLow;
                            JobChain chain = GetJobChain( chainId );

                            // #tbd: variable-length messages support
                            ushort fromJobId = (ushort)evt.Param( 2 );
                            uint depsCount = evt.Param( 2 ) >> 16;

                            for ( uint i = 0; i < depsCount; ++i )
                            {
                                uint toJobId = evt.Param( (int)(3 + i / 2) );
                                if ( i % 2 == 1 )
                                    toJobId >>= 16;
                                chain._deps.Add( new KeyValuePair<ushort, ushort>( fromJobId, (ushort)toJobId ) );
                                Debug.Write( "Dependency: chain=" + chainId + ", fromJob=" + fromJobId + ", toJob=" + (ushort)toJobId + ", t=" + evt.StartTime + ", thread=" + t.Name + "\n" );
                            }
                        }
                        else if ( signalType == SignalType.JobAdded )
                        {
                            ushort id = (ushort)evt.Param( 3 );

                            uint chainIdLow = evt.Param( 1 );
                            uint chainIdHigh = evt.Param( 2 );
                            ulong chainId = ((ulong)chainIdHigh << 32) | chainIdLow;

                            Debug.Write( "Job Add: job=" + id + ", chain=" + chainId + ", t=" + evt.StartTime + ", thread=" + t.Name + "\n" );
                        }
                    }
                }
            }

            // link events
            foreach ( var chainPair in _jobChains )
            {
                JobChain chain = chainPair.Value;
                foreach ( var dep in chain._deps )
                {
                    // TODO
                    if ( !chain._jobs.ContainsKey( dep.Key ) )
                        continue;
                    if ( !chain._jobs.ContainsKey( dep.Value ) )
                        continue;

                    Event start = chain._jobs[dep.Key];
                    Event end = chain._jobs[dep.Value];

                    start.AddDependencyEnd( end );
                    end.AddDependencyStart( start );
                }
            }

            // done
            return true;
        }

        public override string GetDescription( IList<Event> events )
        {
            string txt = "";
            SortedSet<Event> set = new SortedSet<Event>( new Event.Comparer() );
            SortedDictionary<Event, int> eventIndices = new SortedDictionary<Event, int>( new Event.Comparer() );

            foreach ( var e in events )
            {
                JobEvent de = (JobEvent)e;
                set.Add( de );
            }

            txt += string.Format( "Total job events: {0}\n", events.Count );

            Event[] sortedEvents = new Event[events.Count];
            double[] distances = new double[events.Count];
            int[] reversePath = new int[events.Count];

            // initialize distances and clear path
            for ( int i = 0; i < events.Count; ++i )
            {
                distances[i] = double.MinValue;
                reversePath[i] = -1;
            }

            // sort and write initial distances
            int id = 0;
            foreach ( var e in set )
            {
                JobEvent evt = (JobEvent)e;
                sortedEvents[id] = evt;
                eventIndices[evt] = id;
                distances[id] = evt.GetLength();
                id++;
            }

            // accumulate distances
            id = 0;
            foreach ( var e in set )
            {
                JobEvent evt = (JobEvent)e;

                if ( evt.DependencyEnd != null )
                    foreach ( var end in evt.DependencyEnd )
                    {
                        if ( !events.Contains( end ) )
                            continue;

                        int nextId = eventIndices[end];
                        if ( distances[id] + end.GetLength() > distances[nextId] )
                        {
                            reversePath[nextId] = id;
                            distances[nextId] = distances[id] + end.GetLength();
                        }
                    }

                id++;
            }

            // find longest path
            double maxPath = double.MinValue;
            int longestPathEnd = -1;
            for (int i = 0; i < events.Count; ++i )
            {
                if (distances[i] > maxPath)
                {
                    longestPathEnd = i;
                    maxPath = distances[i];
                }
            }

            Stack<Event> criticalPath = new Stack<Event>();
            double criticalPathLength = 0.0;
            while (longestPathEnd != -1)
            {
                Event evt = sortedEvents[longestPathEnd];
                evt.Style = Event.DrawStyle.Highlighted;
                criticalPath.Push( evt );
                criticalPathLength += evt.GetLength();
                longestPathEnd = reversePath[longestPathEnd];
            }


            txt += string.Format( "\nCritical path ({0} events, {1} total)\n",
                criticalPath.Count, Utils.FormatValue( criticalPathLength, ValueType.Time) );
            while (criticalPath.Count > 0)
            {
                Event evt = criticalPath.Pop();
                txt += " - " + evt.GetCaption() + " [" + Utils.FormatValue( evt.GetLength(), ValueType.Time ) + "]\n";
            }

            return txt;
        }

        public override string[] GetStatGraphsNames()
        {
            return null;
        }

        public override string[] GetBlockHitDataNames()
        {
            return new string[] { "File read blocks" };
        }
    }
}
